// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "Utils/VmiUtil.h"

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>

#include "json/cJSON.h"
#include "Log/logging.h"
#include "HWSecure/include/securec.h"
//#include "GLES2/gl2.h"

namespace {
    const std::string WHITE_LIST[] = {"com.android.packageinstaller", ""};
    enum Error : int {
        E_ERROR = -1,
        E_OK = 0,
    };
}

/**
* @brief : determine whether this package is in the white list
* @param [in]packageName: the package name
* @return : false: the package is not in the white list
            true: the package is in the white list
*/
bool IsPkgInWhitelist(const std::string packageName)
{
    bool found = false;
    uint32_t count = 0;
    while (count < sizeof(WHITE_LIST) / sizeof(std::string) && !WHITE_LIST[count].empty()) {
        if (packageName == WHITE_LIST[count]) {
            found = true;
            break;
        }
        count++;
    }
    return found;
}

/**
* @brief : get the process name from string
* @param [in]strLine: string
* @param [out]name: the pointer of process name
* @return : false: get the process name failed
            true: get the process name successfully
*/
bool GetProcessName(const std::string strLine, std::string& name)
{
    const int localuserMaxByteLength = 32;
    char localUser[localuserMaxByteLength] = {0x00};
    uint32_t pid;
    uint32_t ppid;
    uint32_t vsize;
    uint32_t rss;
    const int wchanMaxByteLength = 32;
    char wchan[wchanMaxByteLength] = {0x00};
    const int pcMaxByteLength = 32;
    char pc[pcMaxByteLength] = {0x00};
    const int statusMaxByteLength = 32;
    char status[statusMaxByteLength] = {0x00};
    const int nameMaxByteLength = 512;
    char processName[nameMaxByteLength] = {0x00};
    int result = sscanf_s(strLine.c_str(), "%s %u %u %u %u %s %s %s %s", localUser, localuserMaxByteLength,
        &pid, &ppid, &vsize, &rss, wchan, wchanMaxByteLength, pc, pcMaxByteLength,
        status, statusMaxByteLength, processName, nameMaxByteLength);
    const int scanfFields = 9;
    if (result != scanfFields) {
        ERR("strLine sscanf_s error, strLine = %s", strLine.c_str());
        return false;
    }

    name = processName;
    return true;
}

/**
* @brief : determine whether the process is alive
* @param [in]procName: process name
* @return : false: process is died or process name is error
            true: process is still alive
*/
bool IsProcessAlive(const std::string procName)
{
    if (procName.empty()) {
        ERR("invalid proc_name");
        return false;
    }

    const int cmdMaxByteLength = 256;
    char psGrepAppCmd[cmdMaxByteLength] = {};
    const int maxByteLength = 1024;
    std::string shellOutput;
    int result = sprintf_s(psGrepAppCmd, cmdMaxByteLength, "ps|grep %s", procName.c_str());
    if (result == -1) {
        ERR("sprintf_s error, procName = %s", procName.c_str());
        return false;
    }
    int byteLength = ShellCmd(psGrepAppCmd, shellOutput);
    if (byteLength <= 0) {
        ERR("process_alive: %s is not alive, byteLength = %d", procName.c_str(), byteLength);
        return false;
    }

    char strLine[maxByteLength] = {0};
    int lineLen = 0;
    while (lineLen < byteLength) {
        std::string shellOutputWaiting(shellOutput, lineLen);
        result = sscanf_s(shellOutputWaiting.c_str(), "%[^\n]", strLine, maxByteLength);
        if (result == -1) {
            ERR("shellOutput sscanf_s error, shellOutput = %s", shellOutputWaiting.c_str());
            return false;
        }
        lineLen = lineLen + 1 + strlen(strLine);
        // strLine print format
        // USER      PID   PPID  VSIZE  RSS   WCHAN              PC  NAME
        // root      1     0     9944   2096  SyS_epoll_ 00004c7ed0 S /init
        std::string currentProcName = "";
        bool m_result = GetProcessName(strLine, currentProcName);
        if (m_result == false) {
            ERR("GetProcessName error");
            return false;
        }
        if (currentProcName != "" && currentProcName == procName) {
            INFO("process_alive: %s alive", procName.c_str());
            return true;
        }
        strLine[0] = 0;
    }

    DBG("process_alive: %s is not alive", procName.c_str());
    return false;
}

/**
* @brief : kill the process by package name using killall cmd
* @param [in]package: package name
*/
void KillAllProcess(const std::string package)
{
    if (package.empty()) {
        ERR("package name is null");
        return;
    }
    const int maxByteLength = 512;
    char cmdLine[maxByteLength] = {0};
    int result = sprintf_s(cmdLine, maxByteLength, "killall %s", package.c_str());
    if (result == -1) {
        ERR("sprintf_s error, package = %s", package.c_str());
        return;
    }

    INFO("execute: %s", cmdLine);
    int status = system(cmdLine);
    if (status < 0) {
        ERR("system execute error,status = %d", status);
    }
    INFO("execute end");
}

/**
* @brief : kill the process by package name
* @param [in]package: package name
*/
void KillProcess(const std::string package)
{
    if (package.empty()) {
        ERR("package name is null");
        return;
    }
    const int maxByteLength = 512;
    char cmdLine[maxByteLength] = {0};
    int result = sprintf_s(cmdLine, maxByteLength, "am force-stop %s", package.c_str());
    if (result == -1) {
        ERR("sprintf_s error, package = %s", package.c_str());
        return;
    }
    INFO("execute: %s", cmdLine);
    int status = system(cmdLine);
    if (status < 0) {
        ERR("system execute error, status = %d", status);
    }
    INFO("execute end");
}

/**
* @brief : start the package's process and the activity
* @param [in]package: package name
* @param [in]activity: activity name
*/
void StartProcess(const std::string package, const std::string activity)
{
    if (package.empty() || activity.empty()) {
        ERR("package = %s or activity = %s name is null", package.c_str(), activity.c_str());
        return;
    }
    std::string cmdLine =  "am start ";
    cmdLine += package;
    cmdLine += "/";
    cmdLine += activity;
    INFO("execute: %s\n", cmdLine.c_str());
    int status = system(cmdLine.c_str());
    if (status < 0) {
        ERR("system execute error, status = %d", status);
    }
    INFO("execute:%s end\n", cmdLine.c_str());
}

/**
* @brief : executive shell cmd and save the result
* @param [in]cmdLine: shell command
* @param [out]cmdOutput: result fo execution
* @return : -1: the length of result is too short or command is error
            pos: the length of result
*/
int ShellCmd(const std::string cmdLine, std::string& cmdOutput)
{
    if (cmdLine.empty()) {
        ERR("cmdLine is null");
        return E_ERROR;
    }
    DBG("run [%s]", cmdLine.c_str());

    FILE* fd = popen(cmdLine.c_str(), "r");
    if (fd == nullptr) {
        ERR("run [%s] error\n", cmdLine.c_str());
        return E_ERROR;
    }

    int readChar = 0;
    int position = 0;
    const int maxReadLength = 0x7fffffff;
    while (readChar != EOF && position < maxReadLength) {
        readChar = fgetc(fd);
        cmdOutput += readChar;
        position++;
    };
    int result = pclose(fd);
    if (result < 0) {
        ERR("pclose error %d", result);
        cmdOutput = "";
        return E_ERROR;
    }

    if (position == maxReadLength) {
        ERR("position is equal the max read length");
        cmdOutput = "";
        return E_ERROR;
    }

    DBG("position: %d, cmdOutput:{ %s }\n", position, cmdOutput.c_str());

    return position;
}

/**
* @brief : return home interface
*/
void Restore2Home(void)
{
    const std::string command = "input keyevent KEYCODE_HOME\n";
    INFO("execute: %s\n", command.c_str());
    int status = system(command.c_str());
    if (status < 0) {
        ERR("system execute error, status = %d", status);
    }
    INFO("execute end\n");
}

/**
* @brief : clear all the chain
*/
void IptablesF(void)
{
    const std::string command = "iptables -F\n";
    INFO("execute: %s\n", command.c_str());
    int status = system(command.c_str());
    if (status < 0) {
        ERR("system execute error, status = %d", status);
    }
    INFO("execute end\n");
}

/**
* @brief : write the char to a file
* @param [in]dataPath: first object:the path of file
* @param [in]data: first object:char to be writen
                   second object:the length of char
* @return : char to be writen
*/
const uint8_t* SaveFile(const std::string dataPath, std::pair<const uint8_t*, uint32_t> data)
{
    if (data.first == nullptr || data.second == 0) {
        ERR("data is error, data address = %p, data size = %u", data.first, data.second);
        return nullptr;
    }

    auto realPath = realpath(dataPath.c_str(), nullptr);
    if (realPath == nullptr) {
        ERR("illegal filePath %s", dataPath.c_str());
        return nullptr;
    }

    FILE* psFile = fopen(realPath, "a+b");
    free(realPath);
    realPath = nullptr;
    if (psFile == nullptr) {
        ERR("open File error %s", dataPath.c_str());
        return nullptr;
    }
    uint32_t written = 0;
    while (written < data.second) {
        // second parameter '1' mean the size of data type
        int writeLen = fwrite(data.first + written, 1, data.second - written, psFile);
        if (writeLen > 0) {
            written += writeLen;
        } else {
            ERR("save [ %s ] error, writeLen = %d", dataPath.c_str(), writeLen);
            break;
        }
    }

    int result = fclose(psFile);
    if (result < 0) {
        ERR("fclose error %d", result);
    }
    psFile = nullptr;
    return data.first;
}

/**
* @brief : determine if the key-value is right
* @param [in]cJson: first object:cJSON object
                    second object:input key
* @param [out]value: the pointer of value
* @return : false: the key-value is wrong
            true: the key-value is right
*/
bool GetInt32(std::pair<cJSON *, const uint8_t *> cJson, int& value)
{
    if (cJson.first == nullptr) {
        ERR("cJSON pointer is nullptr");
        return false;
    }
    cJSON *jsonItem = cJSON_GetObjectItem(cJson.first, reinterpret_cast<const char *>(cJson.second));
    if (jsonItem == nullptr) {
        return false;
    }
    value = jsonItem->valueint;
    return true;
}

/**
* @brief : determine if the key-value is right and set the default value if it return false
* @param [in]cJson: first object:cJSON object
                    second object:input key
* @param [out]value: the pointer of value
* @param [in]defaultValue: default value
*/
void GetInt32Default(std::pair<cJSON *, const uint8_t *> cJson, int& value, int defaultValue)
{
    if (false == GetInt32(cJson, value)) {
        ERR("vmi_config error,game not have %s", cJson.second);
        value = defaultValue;
    }
}

/**
* @brief : Calculate call function frequency
*/
void FunctionCallFreq(void)
{
    const uint32_t usecondPerSecond = 1000000;
    static uint64_t lastTime = 0;
    uint64_t currentTime = 0;
    uint64_t diffTime = 0;
    struct timeval time;
    if (lastTime == 0) {
        if (gettimeofday(&time, nullptr) != 0){
            ERR("gettimeofday failed");
        }
        lastTime = time.tv_sec * usecondPerSecond + time.tv_usec;
    } else {
        if (gettimeofday(&time, nullptr) != 0){
            ERR("gettimeofday failed");
        }
        currentTime = time.tv_sec * usecondPerSecond + time.tv_usec;
        diffTime = currentTime - lastTime;
        INFO("FunctionCallFreq time Difference is %llu", diffTime);
        lastTime = currentTime;
    }
}
